The present invention relates generally to system and methods for increasing reliability of software programs. More particularly, the present invention relates to a debugging system and methods which assist software developers with the task of finding and eliminating errors in software programs operative on digital computers.
Before a digital computer may accomplish a desired task, it must receive an appropriate set of instructions. Executed by the computer's microprocessor, these instructions, collectively referred to as a "computer program," direct the operation of the computer. Expectedly, the computer must understand the instructions which it receives before it may undertake the specified activity.
Owing to their digital nature, computers essentially only understand "machine code," i.e., the low-level, minute instructions for performing specific tasks--the sequence of ones and zeros that are interpreted as specific instructions by the computer's microprocessor. Since machine language or machine code is the only language computers actually understand, all other programming languages represent ways of structuring human language so that humans can get computers to perform specific tasks.
While it is possible for humans to compose meaningful programs in machine code, practically all software development today employs one or more of the available programming languages. The most widely used programming languages are the "high-level" languages, such as C or Pascal. These languages allow data structures and algorithms to be expressed in a style of writing which is easily read and understood by fellow programmers.
A program called a "compiler" translates these instructions into the requisite machine language. In the context of this translation, the program written in the high-level language is called the "source code" or source program. The ultimate output of the compiler is an "object module," which includes instructions for execution by a target processor. Although an object module includes code for instructing the operation of a computer, the object module itself is not in a form which may be directly executed by a computer. Instead, it must undergo a "linking" operation before the final executable program is created.
Linking may be thought of as the general process of combining or linking together one or more compiled object modules to create an executable program. This task usually falls to a program called a "linker." In typical operation, a linker receives, either from the user or from an integrated compiler, a list of object modules desired to be included in the link operation. The linker scans the object modules from the object and library files specified. After resolving interconnecting references as needed, the linker constructs an executable image by organizing the object code from the modules of the program in a format understood by the operating system program loader. The end result of linking is executable code (typically an .exe file) which, after testing and quality assurance, is passed to the user with appropriate installation and usage instructions.
During the course of software development, substantial development resources are allocated today to the process of finding "bugs" in the software. As used herein, "bugs" refer to errors occurring in the program being developed. Bugs, for example, can be anything from taking an unexpected path in the logical flow to inadvertently writing to a wrong memory location. Expectedly, there is keen interest in finding ways to improve the "debugging" of software.
A program called a "debugger" is often employed for finding and eliminating errors in software programs. A software program being evaluated by a debugger will be referred to as a "debuggee." Of particular interest to the present invention are debuggers designed for operating systems with graphic user interfaces (GUIs), such as Microsoft.RTM. Windows.TM..
The general topic of debuggers is well covered by the technical, trade, and patent literature. For a detailed introduction to the topic, the reader may consult Swan, T., Mastering Turbo Debugger, Hayden Books, 1990. Additional treatment of the topic may be found in Intel Corporation's 80386 Programmer's Reference Manual, Intel Corp., Santa Clara, Calif., 1986. The disclosures of the foregoing are hereby incorporated by reference.
A debugger should provide certain basic services to assist programmers in finding and correcting errors in software programs. These services include breakpointing, stepping and stopping a debuggee. Other services may include inspecting and changing data in memory, symbolic debugging, source level debugging, and setting breakpoints with expression evaluation.
Of interest to the present invention is "watchpointing," which refers to the ability of the debugger to stop the debuggee when data in memory are altered. Traditionally, watchpointing is accomplished either through the use of debugging registers provided by the hardware or with "single stepping." Single stepping is the process where the debugger instructs the operating system to execute the child process one instruction at a time and to check memory to see if data therein has changed after each statement.
In the first watchpointing method, hardware support in the form of debugging registers is required by the computer. The debugging registers are used to disallow access to selected areas of memory at a "fine grained" level. This "hardware watchpointing" has the advantage of allowing the operating system to execute the debuggee at full speed until the data in the selected memory regions are altered. Although hardware watchpointing is often preferable, it is not always available to the programmer since some computer architectures do not provide the necessary registers or hardware to support it. Even if the hardware supports debugging, watchpointing is usually limited because the number of hardware watchpoints corresponds to the number of debugging registers available. For example, an Intel x86 processor has only four hardware debugging registers. Therefore, a system with an x86 processor supports, at most, four hardware watchpoints. The limited number of watchpoints may impact the effectiveness of the debugger when it is used to debug more complex programs.
A second, "software watchpointing" approach to the foregoing problem is to step through the debuggee one instruction at a time and examine memory to see if the memory contents have been changed after each step. Although the process may be somewhat automated with a debugger, it is time consuming, thus making it impractical for anything but small applications. Moreover, the approach does not inform the user when memory is accessed or read. Sometimes, it is desirable to stop a debuggee whenever a region of memory is accessed, not just when data therein are changed. For example, assume that a bug occurs when the debuggee takes a certain path. Further, assume that the debuggee takes this erroneous path numerous times, but only alters the data therein at the nth pass. Using this watchpointing approach, the error will be detected only after the nth access to that memory location, thus increasing the time needed to find bugs.
Also of interest to the present invention is the notion of "stepping." Stepping refers to process of executing the debuggee one instruction at a time. To step an instruction, the debugger relies on information that links addresses in the object code to line numbers from the source code to identify where in the source code a corresponding statement(s) begins and ends. A problem may arise because some collection of statements, such as library routines, often do not contain line number references.
Currently, developers approach this problem two ways. In the first method, a breakpoint is set at the return address of the subroutine being called. Using this approach, the debugger can run the instruction containing the subroutine at full speed until it returns from the subroutine. A second method is to single step through the subroutine one instruction at a time.
However, both these approaches have pronounced disadvantages. With the first approach, potential problems may arise when a routine without debugging information calls a routine with debugging information. Since the debugger skips over the first routine, the user is not aware that the second routine was ever invoked. Consequently, bugs occurring in the second routine will be difficult to isolate. The second approach, on the other hand, is time consuming and, therefore, has limited appeal.
Also of interest to the present invention is stopping a debuggee from a debugger. Often, a user would like to stop the debuggee during execution to examine the contents of memory or for other purposes. However, stopping the debuggee may be difficult, particularly in a multitasking GUI environment where a debuggee typically spawns numerous threads.
A "thread" as used herein refers to an executable entity that belongs to one and only one process, which in this instance is the debuggee. Generally, a thread comprises a program counter, a user-mode stack, a kernel-mode stack and a set of register values. All threads in a process have equal access to the debuggee's address space, object handles, and other resources. A dispatcher keeps track of the thread schedule, which refers to selecting the order that the threads are executed and initiating context switching from one thread to another.
As shown, stopping a debuggee also entails stopping all of its associated threads. Generally, debuggers stop a debuggee and its threads by looping through all the debuggee's threads and instructing each one to stop executing. This method may take considerable time to finish if there are many threads. More importantly, this can lead to changes in the thread schedule that is maintained by the dispatcher. This may result in a behavior that differs if the debuggee were executed under normal conditions. Consequently, some bugs may be obscured and become hard to find.
Clearly, there is a need for debuggers that reduce or eliminate reliance on hardware support for watchpointing. There is also a need to effectively stop a multi-threaded debuggee without modifying its thread schedule. There is still a further need to provide an efficient way of stepping through statements that have no debugging information. The present invention fulfills these and other needs.